Imports System.Globalization
Imports System
Imports System.Data
Imports System.Collections
Imports System.Reflection
Imports System.Activator
Imports System.Type
Imports System.Collections.Generic


''' -----------------------------------------------------------------------------
''' Project	 : BizBlox
''' Class	 : BizBlox.DataObject
''' 
''' -----------------------------------------------------------------------------
''' <summary>
''' DataObject is the foundation of a BizBlox architecture. Actual data classes 
''' inherit from this. Use the Builder tool to auto generate these inherited
''' classes.
''' </summary>
''' <remarks>
''' The DataObject is designed to be inherited based on a simple
''' pattern as explained in exampleclient.vb code. This object 
''' manages persistance through load and save methods.
''' Remember: the constructor is lazy and will only hit the database when
''' explicitly requested.
''' 
''' </remarks>
''' <history>
''' 	[cinj]	1/09/2004	Created
''' </history>
''' -----------------------------------------------------------------------------
Public MustInherit Class DataObject
    Inherits System.MarshalByRefObject

    'object handles
    Protected m_objDA As DataAccess

    'Data mapping management 
    Private m_uid As System.Guid
    Private m_commitUpdateContext As Boolean
    Private m_touched As Boolean = False


    Public Enum Columns
        DummyColumn
    End Enum

#Region "Caching engine"
    Private Shared m_typeMap As New Dictionary(Of String, DOPropertyHelper)

    Private Shared Function CacheAttributes(ByVal t As Type) As DOPropertyHelper
        Dim h As DOPropertyHelper = Nothing
        If Not m_typeMap.TryGetValue(t.ToString(), h) Then
            SyncLock m_typeMap
                h = New DOPropertyHelper(t)
                m_typeMap.Add(h.DOTypeName, h)
            End SyncLock
        End If
        Return h
    End Function

    Private m_localPropertyHelper As DOPropertyHelper = DataObject.CacheAttributes(Me.GetType())
    Public Function GetPropertyHelper() As DOPropertyHelper
        Return m_localPropertyHelper
    End Function
#End Region

#Region "Constructors"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Create a new object which has a insert context
    ''' The 'context' is determined by the initialisation of the object
    ''' if you initialise without a GUID then the object doesn't map to 
    ''' a row and therefore will require data properties set.
    '''
    ''' Setting the ID property of a DataObject will not change load, save or delete. 
    ''' it is actually just passive READ ONLY.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub New()
        Dim pHelper As DOPropertyHelper = Me.GetPropertyHelper()
        m_commitUpdateContext = False
        m_objDA = New DataAccess
        m_uid = System.Guid.NewGuid()
        Me.GetType().InvokeMember(pHelper.PrimaryKeyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {m_uid})
        m_touched = False
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Create an object from a database lookup - has an update context
    ''' </summary>
    ''' <param name="uid"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub New(ByVal uid As Guid)
        m_commitUpdateContext = True
        m_objDA = New DataAccess
        m_uid = uid
        m_touched = False
    End Sub


    Public Sub New(ByRef baseCol As DOCollection, ByVal index As Integer)
        m_commitUpdateContext = True
        m_objDA = New DataAccess
        m_touched = False
        'Load the UID when loading from the dataset
        m_uid = System.Guid.Empty
        Load(baseCol.ActiveDataSet, index)
    End Sub
#End Region

#Region "Overrides"
    Public MustOverride Function UidColumn() As [Enum]
    Public MustOverride Function TableName() As String
#End Region


#Region "Data Methods"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Commit the object (or its changes) to the database
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Save()
        If m_touched = True Then
            If ExistsInTable() Then
                Update(Nothing)
            Else
                Insert(Nothing)
                m_commitUpdateContext = True
            End If
            m_touched = False
        End If
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Commit the object (or its changes) to the database
    ''' </summary>
    ''' <param name="tObj">Optional: pass in the transaction object</param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Save(ByVal tObj As DOTransaction)
        If m_touched = True Then
            If ExistsInTable() Then
                Update(tObj)
            Else
                Insert(tObj)
                m_commitUpdateContext = True
            End If
            m_touched = False
        End If
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Delete the object from the database.
    ''' </summary>
    ''' <param name="tObj">Optional: pass in the transaction object</param>
    ''' <remarks>
    ''' This item will be able to be saved again if any property is touched after deleting it. 
    ''' Requires appropriate database constraints to be fulfilled or a DOException will be thrown
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Delete(ByVal tObj As DOTransaction)
        Dim sqlStr As String
        sqlStr = "delete from " & TableName() & " where " & UIDColumnName() & "='" & m_uid.ToString() & "'"
        If tObj Is Nothing Then
            m_objDA.RunSQL(sqlStr)
        Else
            tObj.AddSQL(sqlStr)
        End If
        m_touched = False
        m_commitUpdateContext = False
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Delete the object from the database.
    ''' </summary>
    ''' <remarks>
    ''' This item will be able to be saved again if any property is touched after deleting it. 
    ''' Requires appropriate database constraints to be fulfilled or a DOException will be thrown
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Delete()
        Dim sqlStr As String
        sqlStr = "delete from " & TableName() & " where " & UIDColumnName() & "='" & m_uid.ToString() & "'"
        m_objDA.RunSQL(sqlStr)
        m_touched = False
        m_commitUpdateContext = False
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Load the object from the database.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Load()
        Dim sqlStr As String
        Dim tmpds As DataSet
        sqlStr = "select * from " & TableName() & " where " & UIDColumnName() & "='" & m_uid.ToString() & "'"
        tmpds = m_objDA.RunSQLReturnDataSet(sqlStr)
        Load(tmpds, 0)
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Load the object from the dataset.
    ''' </summary>
    ''' <remarks>
    ''' Column names must exactly match the property names of the class being mapped.
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    '''     [cinj]  28/01/2008  Refactor seperated the concern of population from the dataset from database load.
    '''     [cinj]  10/02/2009  Changed typecasting to be coalesced by the runtime
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Private Sub Load(ByRef tmpds As DataSet, ByVal index As Integer)
        Dim pHelper As DOPropertyHelper = Me.GetPropertyHelper()

        If (tmpds.Tables.Count > 0) And (tmpds.Tables(0).Rows.Count > index) Then
            For i As Integer = 0 To pHelper.PropertyInfo.Length - 1
                Dim myInfo As DOInfo = pHelper.GetInfo(i)

                If tmpds.Tables(0).Rows(index).IsNull(pHelper.PropertyInfo(i).PropertyName) = True Then
                    If myInfo.TypeName = "System.String" Then
                        Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {""})
                    ElseIf pHelper.GetInfo(i).TypeName = "System.Guid" Then
                        Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {New Guid("00000000-0000-0000-0000-000000000000")})
                    Else
                        Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {Nothing})
                    End If
                Else
                    Select Case myInfo.TypeName
                        Case "System.DateTime"
                            If ((tmpds.Tables(0).Columns(i).ColumnName().StartsWith("dt_")) Or (tmpds.Tables(0).Columns(i).ColumnName().StartsWith("DT_"))) Then
                                Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {DateTime.ParseExact(CType(tmpds.Tables(0).Rows(index).Item(pHelper.PropertyInfo(i).PropertyName), String), "u", CultureInfo.InvariantCulture.DateTimeFormat)})
                            Else
                                Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {tmpds.Tables(0).Rows(index).Item(pHelper.PropertyInfo(i).PropertyName)})
                            End If
                        Case "System.Guid"
                            Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(index).Item(pHelper.PropertyInfo(i).PropertyName), System.Guid)})
                            If myInfo.PrimaryKey Then
                                Me.m_uid = CType(tmpds.Tables(0).Rows(index).Item(pHelper.PropertyInfo(i).PropertyName), System.Guid)
                            End If
                        Case Else
                            Me.GetType().InvokeMember(pHelper.PropertyInfo(i).PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {tmpds.Tables(0).Rows(index).Item(pHelper.PropertyInfo(i).PropertyName)})
                    End Select
                End If
            Next
            m_commitUpdateContext = True
        Else
            m_commitUpdateContext = False
            Throw New DOException("Failed load. DataObject does not exist in the database.")
        End If
        m_touched = False
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' copy all properties to a new object of the same type (except the UID)
    ''' </summary>
    ''' <param name="srcObj"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Copy(ByVal srcObj As Object)
        Dim myPropertyInfo() As PropertyInfo
        Dim tmpCol As String
        myPropertyInfo = Me.GetType().GetProperties()
        For i As Integer = 0 To myPropertyInfo.Length - 1
            tmpCol = myPropertyInfo(i).Name
            If Not tmpCol = UIDColumnName() Then
                Me.GetType().InvokeMember(tmpCol, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(Me.GetType().InvokeMember(tmpCol, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, srcObj, New Object() {}), String)})
            End If
        Next i
    End Sub



    ''' <summary>
    ''' Prep output for SQL commit
    ''' 
    ''' </summary>
    ''' <param name="info"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' 	[cinj]	29/07/2004	Created
    '''     [cinj]  10/02/2009  Updated to take DOInfo and use native tostring operations
    ''' </remarks>
    Private Function ReadProperty(ByVal info As DOInfo) As String
        Dim xObj As Object = Me.GetType().InvokeMember(info.PropertyName, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {})

        Select Case info.TypeName
            Case "System.Guid"
                Dim xStr As String = CType(xObj, System.Guid).ToString()
                If xStr = "00000000-0000-0000-0000-000000000000" Then Return Nothing
                Return xStr
            Case "System.Boolean"
                If CType(xObj, Boolean) = True Then
                    Return "1"
                Else
                    Return "0"
                End If
            Case "System.DateTime"
                Dim dtStr As String = CType(xObj, DateTime).ToString("yyyy-MM-dd HH:mm:ssZ")
                If dtStr = "0001-01-01 00:00:00Z" Then Return Nothing
                Return dtStr
            Case "System.String"
                If xObj Is Nothing Then Return ""
                Return CType(xObj, String)
            Case Else
                If xObj Is Nothing Then Return Nothing
                Return xObj.ToString()
        End Select
    End Function

    Private Sub Insert(Optional ByRef tObj As DOTransaction = Nothing)
        Dim tmpVal As String
        Dim sqlStr As String
        Dim pHelper As DOPropertyHelper = Me.GetPropertyHelper()


        sqlStr = "insert into [" & TableName() & "]("
        Dim i As Integer
        Dim startedFlag As Boolean

        'set up insert sql here
        startedFlag = False
        For i = 0 To pHelper.PropertyInfo.Length - 1
            If startedFlag = False Then
                startedFlag = True
            Else
                sqlStr = sqlStr & ","
            End If
            sqlStr = sqlStr & pHelper.PropertyInfo(i).PropertyName
        Next i
        sqlStr = sqlStr & ") values ("
        startedFlag = False
        For i = 0 To pHelper.PropertyInfo.Length - 1
            If startedFlag = False Then
                startedFlag = True
            Else
                sqlStr = sqlStr & ","
            End If

            Dim myInfo As DOInfo = pHelper.PropertyInfo(i)
            tmpVal = ReadProperty(myInfo)
            If tmpVal Is Nothing Then
                sqlStr &= " NULL "
            Else
                If myInfo.TypeName = "System.String" Then
                    sqlStr &= "N"
                End If
                sqlStr &= "'" & Replace(tmpVal, "'", "''") & "'"
            End If
        Next i
        sqlStr = sqlStr & ")"
        If tObj Is Nothing Then
            m_objDA.RunSQL(sqlStr)
        Else
            tObj.AddSQL(sqlStr)
        End If
    End Sub

    Private Sub Update(Optional ByRef tObj As DOTransaction = Nothing)
        Dim tmpVal As String
        Dim sqlStr As String
        Dim pHelper As DOPropertyHelper = Me.GetPropertyHelper()
        Dim myInfo As DOInfo

        sqlStr = "update [" & TableName() & "] set "
        Dim i As Integer
        Dim startedFlag As Boolean

        'set up insert sql here
        startedFlag = False
        For i = 0 To pHelper.PropertyInfo.Length - 1
            myInfo = pHelper.PropertyInfo(i)
            If (Not myInfo.PrimaryKey) And (Not myInfo.Identity) Then
                If startedFlag = True Then
                    sqlStr = sqlStr & ","
                Else
                    startedFlag = True
                End If

                tmpVal = ReadProperty(myInfo)
                sqlStr = sqlStr & myInfo.PropertyName & " = "
                If tmpVal Is Nothing Then 'And (Not tmpCol.StartsWith("s_")) Then
                    sqlStr = sqlStr & " NULL "
                Else
                    If myInfo.TypeName = "System.String" Then
                        sqlStr &= "N"
                    End If
                    sqlStr &= "'" & Replace(tmpVal, "'", "''") & "'"
                End If
            End If
        Next i
        'key the update against this UID.
        sqlStr = sqlStr & " where " & pHelper.PrimaryKeyName & "='" & Me.ReadProperty(pHelper.GetInfo(pHelper.PrimaryKeyName)) & "'"
        If tObj Is Nothing Then
            m_objDA.RunSQL(sqlStr)
        Else
            tObj.AddSQL(sqlStr)

        End If
    End Sub
#End Region

#Region "Special Properties Methods"
    'get this object UID
    <Obsolete("The function should not be used any longer. The design of BizBlox is shifting in future releases to allow other data types as primary keys")> _
    Public Function Uid() As Guid
        Return m_uid
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Does this object exist in the table it maps to?
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function ExistsInTable() As Boolean
        Return m_commitUpdateContext
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Determine if a value is EQUIVALENT to a null.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Shared Function IsNull(ByVal dtVal As DateTime) As Boolean
        If dtVal = Nothing Then Return True
        Dim dtStr As String = dtVal.ToString("yyyy-MM-dd HH:mm:ssZ")
        If dtStr = "0001-01-01 00:00:00Z" Then Return True
        Return False
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Determine if a value is EQUIVALENT to a null.
    ''' </summary>
    ''' <param name="gVal"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Shared Function IsNull(ByVal gVal As Guid) As Boolean
        If gVal.ToString() = "00000000-0000-0000-0000-000000000000" Then Return True
        Return False
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Get the column name of the Primary Key (UniqueID)
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    <Obsolete("This property should not be used by client code any longer. the DOInfo should be used instead.")> _
    Public Function UIDColumnName() As String
        Return UidColumn().ToString()
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Get a data transfer object of the type defined by [toType] and populate it from
    ''' the data contained in the DataObject
    ''' </summary>
    ''' <param name="toType"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function GetTO(ByVal toType As Type) As DOTO
        Dim myPropertyInfo() As PropertyInfo = Me.GetType().GetProperties()
        Dim toObj As DOTO
        toObj = Activator.CreateInstance(toType)
        For i As Integer = 0 To myPropertyInfo.Length - 1
            If Not toType.GetProperty(myPropertyInfo(i).Name) Is Nothing Then
                toType.InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, toObj, New Object() {Me.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {})})
            End If
        Next i
        Return toObj
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Accept a transfer object (which inherits from DOTO) to populate this DataObject
    ''' </summary>
    ''' <param name="toObj"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub AcceptTO(ByVal toObj As DOTO)
        Dim myPropertyInfo() As PropertyInfo = Me.GetType().GetProperties()
        For i As Integer = 0 To myPropertyInfo.Length - 1
            If Not toObj.GetType().GetProperty(myPropertyInfo(i).Name) Is Nothing Then
                Try
                    If Not myPropertyInfo(i).Name = Me.UIDColumnName() Then
                        Me.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {toObj.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, toObj, New Object() {})})
                    End If
                Catch ex As Exception

                End Try
            End If
        Next i
    End Sub
#End Region

#Region "SubClass Utility Method"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' all subclassed property SET methods *must* call this 
    ''' subroutine to flag that the object has changed and 
    ''' needs to be committed to the database.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Protected Sub Touch()
        m_touched = True
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Check if a string extends beyond a predefined length - throw exception if it does
    ''' </summary>
    ''' <param name="param"></param>
    ''' <param name="input"></param>
    ''' <param name="limit"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    ''' 
    Protected Sub BoundsCheck(ByVal param As [Enum], ByVal input As String, ByVal limit As Integer)
        If input Is Nothing Then Return
        If input.Length > limit Then Throw New ArgumentOutOfRangeException(param.ToString(), input, "This field is limited to " & limit & " characters.")
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Check if a date extends beyond a predefined length when stored as string - throw exception if it does
    ''' </summary>
    ''' <param name="param"></param>
    ''' <param name="input"></param>
    ''' <param name="limit"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    ''' 
    Protected Sub BoundsCheck(ByVal param As [Enum], ByVal input As DateTime, ByVal limit As Integer)
        Dim tmp As String = input.ToString("yyyy-MM-dd HH:mm:ssZ")
        If tmp.Length > limit Then Throw New ArgumentOutOfRangeException(param.ToString(), input, "This field is limited to " & limit & " characters.")
    End Sub

#End Region

End Class

